package com.weimob.mcloud.wechatwork.customer.mq.model;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.weimob.mcloud.mcloudratelimitercore.common.RatelimiterInfo;
import com.weimob.mcloud.mcloudratelimitercore.common.enums.LimitKeyEnum;
import com.weimob.mcloud.mcloudratelimitercore.common.utils.LimitKeyUtils;
import com.weimob.mcloud.wechatwork.common.enums.CommonBizErrorEnum;
import com.weimob.mcloud.wechatwork.common.enums.MainBizDomainEnum;
import com.weimob.mcloud.wechatwork.common.enums.SubBizDomainEnum;
import com.weimob.mcloud.wechatwork.common.utils.BizErrorCodeGenerator;
import com.weimob.mcloud.wechatwork.common.utils.CollUtils;
import com.weimob.mcloud.wechatwork.core.adapter.pojo.dto.bos.request.QueryBosRequestDTO;
import com.weimob.mcloud.wechatwork.core.adapter.pojo.dto.bos.response.MerchantDTO;
import com.weimob.mcloud.wechatwork.core.adapter.pojo.dto.channel.request.CustomerGroupDetailReqDTO;
import com.weimob.mcloud.wechatwork.core.adapter.pojo.dto.channel.response.CustomerGroupDetailResBO;
import com.weimob.mcloud.wechatwork.core.adapter.pojo.dto.channel.response.ExternalUseridToPendingIdRespDTO;
import com.weimob.mcloud.wechatwork.core.adapter.pojo.dto.ec.request.BatchMigrationStatusRequest;
import com.weimob.mcloud.wechatwork.core.adapter.pojo.dto.ec.response.BatchMigrationStatusVo;
import com.weimob.mcloud.wechatwork.core.adapter.pojo.dto.onecrm.request.WidCreateReq;
import com.weimob.mcloud.wechatwork.core.adapter.pojo.dto.onecrm.response.WidCreateBO;
import com.weimob.mcloud.wechatwork.core.adapter.service.BosAdapterService;
import com.weimob.mcloud.wechatwork.core.adapter.service.ChannelAdapterService;
import com.weimob.mcloud.wechatwork.core.adapter.service.EcAdapterService;
import com.weimob.mcloud.wechatwork.core.base.SingleResponse;
import com.weimob.mcloud.wechatwork.core.entity.privatesphere.PrivateSphere;
import com.weimob.mcloud.wechatwork.core.entity.relation.employee.Employee;
import com.weimob.mcloud.wechatwork.core.entity.relation.group.CustomerGroup;
import com.weimob.mcloud.wechatwork.core.entity.relation.group.CustomerGroupMember;
import com.weimob.mcloud.wechatwork.core.enums.employee.EmployeeActiveStatusEnum;
import com.weimob.mcloud.wechatwork.core.enums.employee.EmployeeStatusEnum;
import com.weimob.mcloud.wechatwork.core.enums.group.*;
import com.weimob.mcloud.wechatwork.core.enums.privateSphere.PrivateSphereEncryptCorpEnum;
import com.weimob.mcloud.wechatwork.core.service.group.request.CustomerGroupCreateRequest;
import com.weimob.mcloud.wechatwork.core.service.group.request.CustomerGroupMemberCreateListRequest;
import com.weimob.mcloud.wechatwork.core.service.group.request.CustomerGroupMemberDTO;
import com.weimob.mcloud.wechatwork.core.service.group.request.CustomerGroupWidCreateRecordDTO;
import com.weimob.mcloud.wechatwork.customer.constant.RedisLockConstant;
import com.weimob.mcloud.wechatwork.customer.domain.ArtemisBaseInfo;
import com.weimob.mcloud.wechatwork.customer.domain.ArtemisConstant;
import com.weimob.mcloud.wechatwork.customer.domain.IpaasMsgConstant;
import com.weimob.mcloud.wechatwork.customer.externalDependencyProxy.onecrm.OneCrmProxy;
import com.weimob.mcloud.wechatwork.customer.externalDependencyProxy.usermanagement.EmployeeProxy;
import com.weimob.mcloud.wechatwork.customer.listener.event.CustomerGroupMemberEvent;
import com.weimob.mcloud.wechatwork.customer.mq.JoinGroupMsgConvert;
import com.weimob.mcloud.wechatwork.customer.mq.constant.GroupUpdateDetailConstant;
import com.weimob.mcloud.wechatwork.customer.mq.dto.CorpChatGroupDTO;
import com.weimob.mcloud.wechatwork.customer.mq.dto.ExternalUserNumModel;
import com.weimob.mcloud.wechatwork.customer.mq.enumeration.GroupRecordEnum;
import com.weimob.mcloud.wechatwork.customer.mq.handler.GroupMemberEventEnum;
import com.weimob.mcloud.wechatwork.customer.service.impl.ArtemisService;
import com.weimob.mcloud.wechatwork.customer.service.user.exception.BizException;
import com.weimob.mcloud.wechatwork.customer.service.user.module.enums.group.ChatGroupMemberTypeEnum;
import com.weimob.mcloud.wechatwork.customer.service.user.module.enums.group.ChatGroupStateEnum;
import com.weimob.mcloud.wechatwork.customer.third.ChannelAdapterServiceProxy;
import com.weimob.mcloud.wechatwork.customer.third.PrivateSphereThird;
import com.weimob.mcloud.wechatwork.customer.third.core.CustomerGroupExportServiceProxy;
import com.weimob.saas.open.producer.interfaces.dto.OpenMessageDTO;
import com.weimob.saas.open.producer.interfaces.export.OpenMessageDubboService;
import com.weimob.soa.common.response.SoaResponse;
import com.weimob.soa.common.utils.SoaUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * @author liuzhu.chen
 * @desc 客户群service
 */
@Service
@Slf4j
public class CorpChatGroupServiceImpl {

    @Resource
    private WechatworkBaseArtemisProducer wechatworkBaseArtemisProducer;

    @Autowired
    private ChannelAdapterService channelAdapterService;

    @Resource
    private PrivateSphereThird privateSphereThird;

    @Reference
    private EcAdapterService ecAdapterService;

    @Resource
    ApplicationContext applicationContext;

    @Resource
    private ArtemisService artemisService;

    @Resource
    RedisTemplate<String,String> redisTemplate;

    @Resource
    CustomerGroupStatisticServiceImpl customerGroupStatisticService;

    @Resource(name = "customerGroupMemberStatisticThreadPool")
    ThreadPoolTaskExecutor customerGroupMemberStatisticThreadPool;

    @Resource(name = "createGroupMemberWidThreadPool")
    private ThreadPoolTaskExecutor memberWidThreadPool;


    @Value("${groupmember.sync.partitioncount:20}")
    private Integer partitionCount;

    @Value("${sendFullPassport:true}")
    private Boolean sendFullPassport;

    @Reference
    private BosAdapterService bosAdapterService;

    @Value("${group.sync.initiative.exception:false}")
    private Boolean initiativeException;

    @Autowired
    OneCrmProxy oneCrmProxy;

    @Resource
    CustomerGroupExportServiceProxy customerGroupExportServiceProxy;

    @Resource
    EmployeeProxy employeeProxy;

    @Resource
    ChannelAdapterServiceProxy  channelAdapterServiceProxy;

    @Reference
    OpenMessageDubboService openMessageDubboService;

    /**
     * 群创建失败errorCode
     */
    String errorCode = BizErrorCodeGenerator.generate(MainBizDomainEnum.CUSTOMER_MANAGEMENT, SubBizDomainEnum.GROUP_MANAGEMENT, CommonBizErrorEnum.ILLEGAL_ARGUMENTS);

    @ApolloJsonValue("${normal.error.codes:[701008,30000000000002,100300600131,100300600115]}")
    private List<Long> normalErrorCodes;

    /**
     * 客户群创建
     */
    public SoaResponse<?,?> create(CorpChatGroupDTO dto, PrivateSphere privateSphere) {
        if(Objects.isNull(dto) || Objects.isNull(dto.getCorpId()) || Objects.isNull(dto.getChatId())){
            log.error("CorpChatGroupServiceImpl.create 客户群创建参数异常");
            return SoaUtil.error(errorCode, null, null);
        }
        log.info("客户群创建,dto:{}", dto);

        // 获取私域
        if(Objects.isNull(privateSphere)){
            privateSphere = privateSphereThird.fetch(dto.getCorpId(), dto.getTemplateId());
            if (Objects.isNull(privateSphere) || StringUtils.isEmpty(privateSphere.getPrivateSphereId())) {
                log.info("客户群创建，未查到私域信息，不进行客户数据同步，dto:{}", dto);
                return SoaUtil.error(errorCode, null, null);
            }
        }

        // 1.查询DB是否已存在这个chatId的客户群记录
        if (isExistGroupChat(privateSphere, dto.getChatId())) {
            // I.可能是历史数据，重新进行了同步，算作群变更
           return update(dto,privateSphere);
        }
        // 尝试获取分布式锁
        String redisKey = String.format(RedisLockConstant.CUSTOMER_GROUP_SYNC_LOCK, privateSphere.getBosId(), dto.getChatId());
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(redisKey, String.valueOf(System.currentTimeMillis()), RedisLockConstant.CUSTOMER_GROUP_SYNC_LOCK_EXPIRE, TimeUnit.SECONDS);
        if (!Boolean.TRUE.equals(flag)) {
            log.info("客户群创建，尝试获取分布式锁失败，当前客户群正在同步中dto:{}", dto);
            return SoaUtil.error(errorCode, null, null);
        }
        try {
            log.info("客户群创建，群未创建开始创建,dto:{}", dto);
            // 2.远程查询企微客户群数据（腾讯）
            SoaResponse<CustomerGroupDetailResBO, Void> soaResponse = getCustomerGroupDetailResBO(dto, privateSphere);
            CustomerGroupDetailResBO groupChat = soaResponse.getResponseVo();
            if (Objects.isNull(groupChat)) {
                log.warn("客户群创建，查询企微接口未获得数据,dto:{}", dto);
                return SoaUtil.error(soaResponse.getReturnCode(), null, null);
            }
            // 3、根据TX返回的群主UserID 先查下群主信息
            Employee owner = employeeProxy.fetchEmployeeById(privateSphere, groupChat.getOwner(), null);
            GroupOwnerModel groupOwnerModel = getOwnerInfo(owner);

            // 4、取出客户群里的成员列表(腾讯)
            List<CustomerGroupMemberDTO> txMemberList = getTxMemberList(groupChat, privateSphere);
            // 统计客户群里的群成员总数，外部联系人总数、有效外部联系人（手机号不为空）总数
            ExternalUserNumModel externalUserNumModel = getExternalUserNum(txMemberList);

            // 5、更新DB里的客户群基础信息
            CustomerGroupCreateRequest customerGroupCreateRequest = getCustomerGroupCreateRequest(dto.getCorpId(), groupChat, groupOwnerModel, externalUserNumModel, null, null, privateSphere);
            Integer n = customerGroupExportServiceProxy.createCustomerGroup(customerGroupCreateRequest);
            if (n == 0) {
                log.error("客户群创建失败，customerGroupCreateRequest:{}", JSON.toJSONString(customerGroupCreateRequest));
                return SoaUtil.error(errorCode, null, null);
            }

            // 6、更新DB里的客户群成员
            if (CollectionUtils.isNotEmpty(txMemberList)) {
                // 批量插入or更新
                CustomerGroupMemberCreateListRequest customerGroupMemberCreateListRequest = new CustomerGroupMemberCreateListRequest();
                customerGroupMemberCreateListRequest.setPrivateSphere(privateSphere);
                customerGroupMemberCreateListRequest.setMemberList(txMemberList);
                Boolean result = customerGroupExportServiceProxy.batchCreateUpdateGroupMember(customerGroupMemberCreateListRequest);
                if(!Boolean.TRUE.equals(result)){
                    log.error("CorpChatGroupServiceImpl.create 插入/更新更新客户群成员失败");
                }
                // 将群成员中客户数据送到passPort & 发送wid创建完成消息
                sendPassPortAndNotify(privateSphere, txMemberList, dto);
            }
            log.info("客户群创建成功,dto:{}", dto);

            // 7、后续动作
            createAfter(dto, privateSphere, txMemberList, customerGroupCreateRequest);

            return SoaUtil.ok(null);
        } catch (BizException e) {
            if (normalErrorCodes.contains(e.getErrcode())) {
                log.info("正常业务逻辑下的客户群创建失败，exception:", e);
            } else {
                log.error("客户群创建失败，exception:", e);
            }
            return SoaUtil.error(errorCode, null, null);
        } catch (Exception e) {
            log.error("客户群创建失败，exception:", e);
            return SoaUtil.error(errorCode, null, null);
        }
        finally {
            redisTemplate.delete(redisKey);
        }
    }

    /**
     * 创建客户群后续动作
     * @param dto
     * @param privateSphere
     * @param txMemberList
     * @param customerGroupCreateRequest
     */
    private void createAfter(CorpChatGroupDTO dto, PrivateSphere privateSphere, List<CustomerGroupMemberDTO> txMemberList, CustomerGroupCreateRequest customerGroupCreateRequest) {
        // 收集群中客户
        List<CustomerGroupMemberDTO> customerList = txMemberList.stream()
                .filter(e -> GroupMemberTypeEnum.EXTERNAL_USER.getCode().equals(e.getType()))
                .collect(Collectors.toList());
        if(CollectionUtils.isNotEmpty(customerList)){
            // CDP打点 客户群创建事件打点订阅（实时消息才触发向CDP打点）
            if (!Boolean.TRUE.equals(dto.getIsInitGroup())) {
                CustomerGroupMemberEvent customerGroupMemberEvent = new CustomerGroupMemberEvent(GroupMemberEventEnum.JOIN_GROUP.getCode(), privateSphere, customerGroupCreateRequest, customerList);
                applicationContext.publishEvent(customerGroupMemberEvent);
            }
            // 记录今日进群客户
            List<CustomerGroupMemberDTO> joinMemberList = customerList.stream()
                    .filter(e -> isToday(e.getJoinTime()))
                    .collect(Collectors.toList());
            recordJoinQuitGroup(privateSphere, dto, joinMemberList, GroupRecordEnum.JOIN_GROUP);
            // 更新群成员好友标识
            PrivateSphere finalPrivateSphere = privateSphere;
            customerGroupMemberStatisticThreadPool.execute(() -> customerGroupStatisticService.updateFriendIdentification(customerList, finalPrivateSphere));
        }
    }

    /**
     * 查询腾讯客户群详情
     * @param dto
     * @param privateSphere
     * @return
     */
    private SoaResponse<CustomerGroupDetailResBO, Void> getCustomerGroupDetailResBO(CorpChatGroupDTO dto, PrivateSphere privateSphere) {
        CustomerGroupDetailReqDTO customerGroupDetailReqDTO = new CustomerGroupDetailReqDTO();
        customerGroupDetailReqDTO.setChatId(dto.getChatId());
        customerGroupDetailReqDTO.setCorpId(dto.getCorpId());
        customerGroupDetailReqDTO.setTemplateId(privateSphere.getTemplateId());
        customerGroupDetailReqDTO.setProviderIdentity(privateSphere.getChannelProviderIdentity());
        SoaResponse<CustomerGroupDetailResBO, Void> soaResponse = channelAdapterService.getGroupChat(customerGroupDetailReqDTO);
        String returnCode = soaResponse.getReturnCode();
        // 接入限频
        if (CommonBizErrorEnum.WECHAT_WORK_LIMIT.getCode().equals(returnCode) || CommonBizErrorEnum.WECHAT_WORK_CONVERT_ID_LIMIT.getCode().equals(returnCode)) {
            CustomerGroupDetailResBO groupDetailResBO = soaResponse.getResponseVo();
            if (Objects.nonNull(groupDetailResBO)) {
                Long taskId = groupDetailResBO.getTaskId();
                log.info("调用腾讯客户群详情接口被限频，taskId:{},msg：{}", taskId, soaResponse.getReturnMsg());
                // 创建一个限频的redis任务记录
                RatelimiterInfo ratelimiterInfo = new RatelimiterInfo(LimitKeyEnum.CUSTOMER_GROUP_GET.getKey(), JSON.toJSONString(dto));
                redisTemplate.opsForValue().set(LimitKeyUtils.buildRedisKey(taskId), JSON.toJSONString(ratelimiterInfo), 1, TimeUnit.DAYS);
            }
        }
        return soaResponse;
    }

    private CustomerGroupCreateRequest getCustomerGroupCreateRequest(String corpId, CustomerGroupDetailResBO groupChat, GroupOwnerModel ownerInfo, ExternalUserNumModel externalUserNumModel,String id,String ownerId,PrivateSphere privateSphere) {
        CustomerGroupCreateRequest customerGroupCreateRequest = new CustomerGroupCreateRequest();
        if(Objects.nonNull(id)){
            customerGroupCreateRequest.setId(Long.parseLong(id));
        }
        customerGroupCreateRequest.setOwnerId(ownerId);
        customerGroupCreateRequest.setCorpId(corpId);
        customerGroupCreateRequest.setPrivateSphere(privateSphere);
        customerGroupCreateRequest.setGroupId(groupChat.getChatId());
        customerGroupCreateRequest.setNotice(groupChat.getNotice());
        customerGroupCreateRequest.setOwner(groupChat.getOwner());
        customerGroupCreateRequest.setName(groupChat.getName());
        customerGroupCreateRequest.setState(ChatGroupStateEnum.GENERAL.getValue());
        customerGroupCreateRequest.setExternalUserNum(externalUserNumModel.getExternalUserNum());
        customerGroupCreateRequest.setValidExternalUserNum(externalUserNumModel.getValidExternalUserNum());
        customerGroupCreateRequest.setNum(CollectionUtils.isEmpty(groupChat.getMemberList()) ? 0 : groupChat.getMemberList().size());
        if(Objects.nonNull(groupChat.getCreateTime())){
            customerGroupCreateRequest.setGroupCreateTime(new Date(groupChat.getCreateTime() * 1000L));
        }
        customerGroupCreateRequest.setAuthorizationStatus(ownerInfo.getAuthorizationStatus());
        customerGroupCreateRequest.setGroupType(CustomerGroupGroupTypeEnum.CUSTOMER_GROUP.getCode());
        customerGroupCreateRequest.setVidList(ownerInfo.getVidList());
        return customerGroupCreateRequest;
    }

    /**
     * 将群成员中客户数据送到passPort & 发送wid创建完成消息
     * @param sendPassportMemberList
     * @param groupDTO
     * @param privateSphere
     */
    private void sendToPassPortAndNotify(List<CustomerGroupMemberDTO> sendPassportMemberList, CorpChatGroupDTO groupDTO, PrivateSphere privateSphere) {
        if (CollectionUtils.isEmpty(sendPassportMemberList)) {
            return;
        }
        // 密文体系时 -> 批量换下pendingId
        Map<String, String> pendingMap = new HashMap<>();
        if(PrivateSphereEncryptCorpEnum.CIPHER.getCode().equals(privateSphere.getEncryptCorpId())){
            List<String> externalUserIds = sendPassportMemberList.stream()
                    .filter(e -> GroupMemberTypeEnum.EXTERNAL_USER.getCode().equals(e.getType()) && StringUtils.isNotBlank(e.getMemberId()) && StringUtils.isBlank(e.getUnionid()))
                    .map(CustomerGroupMemberDTO::getMemberId)
                    .collect(Collectors.toList());

            if(CollectionUtils.isNotEmpty(externalUserIds)){
                List<ExternalUseridToPendingIdRespDTO.Result> pendingIdList = channelAdapterServiceProxy.getPendingIdList(groupDTO, privateSphere, externalUserIds);
                if(CollectionUtils.isNotEmpty(pendingIdList)){
                    pendingMap = pendingIdList.stream().collect(
                            Collectors.toMap(ExternalUseridToPendingIdRespDTO.Result::getExternalUserid, ExternalUseridToPendingIdRespDTO.Result::getPendingId, (k1, k2) -> k1));
                }
            }
        }

        // 分组
        Map<String, String> finalPendingMap = pendingMap;
        List<List<CustomerGroupMemberDTO>> partitions = Lists.partition(sendPassportMemberList, 10);
        for (List<CustomerGroupMemberDTO> memberDTOList : partitions) {
            memberWidThreadPool.execute(() -> {
                List<CustomerGroupWidCreateRecordDTO> failRecordList = new ArrayList<>();
                for (int i = 0; i < memberDTOList.size(); i++) {
                    CustomerGroupMemberDTO memberDTO = memberDTOList.get(i);
                    try {
                        // 主动报错方便测试
                        if(Boolean.TRUE.equals(initiativeException)){
                            throw new BizException("主动报错异常测试使用");
                        }
                        // 1.报送PassPort
                        WidCreateBO widCreateBO = createWid(privateSphere,memberDTO, finalPendingMap.get(memberDTO.getMemberId()));
                        // 2.发送wid创建完成消息
                        if (Objects.nonNull(widCreateBO) && Objects.nonNull(widCreateBO.getWid())) {
                            sendCompleteMsg(memberDTO, groupDTO);
                            customerJoinGroupSecondMsg(memberDTO, privateSphere, widCreateBO.getWid());
                        } else {
                            // 3.构建失败记录对象
                            CustomerGroupWidCreateRecordDTO recordDTO = buildFailRecord(privateSphere, memberDTO, groupDTO);
                            failRecordList.add(recordDTO);
                        }
                    } catch (Exception e) {
                        log.error("sendToPassPortAndNotify 客户群同步，创建wid失败 error:",e);
                        // 3.构建失败记录对象
                        CustomerGroupWidCreateRecordDTO recordDTO = buildFailRecord(privateSphere, memberDTO, groupDTO);
                        failRecordList.add(recordDTO);
                    }
                }
                if(CollectionUtils.isNotEmpty(failRecordList)){
                    // 批量插入失败重试记录表
                    customerGroupExportServiceProxy.batchSaveWidCreateRecord(failRecordList);
                }
            });
        }
    }


    /**
     * 失败记录
     * @param privateSphere
     * @param memberDTO
     * @param groupDTO
     */
    private CustomerGroupWidCreateRecordDTO buildFailRecord(PrivateSphere privateSphere, CustomerGroupMemberDTO memberDTO, CorpChatGroupDTO groupDTO){
        CustomerGroupMemberList customerGroupMemberList = composeCustomerGroupMemberList(memberDTO,groupDTO);
        CustomerGroupWidCreateRecordDTO failRecord = new CustomerGroupWidCreateRecordDTO();
        failRecord.setBosId(privateSphere.getBosId());
        failRecord.setCorpId(privateSphere.getCorpId());
        failRecord.setGroupId(groupDTO.getChatId());
        failRecord.setExternalUserId(memberDTO.getMemberId());
        failRecord.setUnionId(memberDTO.getUnionid());
        failRecord.setNickname(memberDTO.getNickname());
        failRecord.setStatus(RetryStatusEnum.WAIT_RETRY);
        failRecord.setCompletedMsg(JSON.toJSONString(customerGroupMemberList));
        failRecord.setDeleted(false);
        return failRecord;
    }

    /**
     * 将客户群成员中的客户送到passPort生成wid
     * 目的是：非员工好友也能存在wid
     * @param privateSphere
     * @param memberDTO
     * @param pendingId
     * @return
     */
    private WidCreateBO createWid(PrivateSphere privateSphere,CustomerGroupMemberDTO memberDTO,String pendingId){
        if(StringUtils.isBlank(pendingId) && StringUtils.isBlank(memberDTO.getUnionid())){
            log.info("createWid unionId & pendingId 同时为空");
        }
        WidCreateReq widCreateReq = new WidCreateReq();
        widCreateReq.setBosId(privateSphere.getBosId());
        widCreateReq.setCorpId(privateSphere.getCorpId());
        widCreateReq.setExternalUserId(memberDTO.getMemberId());
        String unionId = memberDTO.getUnionid();
        if(PrivateSphereEncryptCorpEnum.CIPHER.getCode().equals(privateSphere.getEncryptCorpId())
                && StringUtils.isBlank(unionId)
                && StringUtils.isNotBlank(pendingId)){
            widCreateReq.setPendingId(pendingId);
        }else {
            widCreateReq.setUnionId(unionId);
        }
        widCreateReq.setNickname(memberDTO.getNickname());
        return oneCrmProxy.createWid(widCreateReq);
    }

    /**
     * 发送wid创建完成消息
     * @param memberDTO
     * @param groupDTO
     */
    private void sendCompleteMsg(CustomerGroupMemberDTO memberDTO, CorpChatGroupDTO groupDTO){
        CustomerGroupMemberList customerGroupMemberList = composeCustomerGroupMemberList(memberDTO,groupDTO);
        ArtemisBaseInfo artemisBaseDomain = new ArtemisBaseInfo();
        artemisBaseDomain.setTopic(ArtemisConstant.Topic.WECHATWORK_GROUP_MEMBER_UPDATE_MSG);
        artemisBaseDomain.setProducer(ArtemisConstant.USER);
        artemisBaseDomain.setEvent(JSONObject.toJSONString(customerGroupMemberList));
        artemisService.sendStringEventToArtemis(artemisBaseDomain);
    }

    /**
     * 客户入群消息二次分发 对接开放平台
     * @param memberDTO
     * @param wid
     */
    public void customerJoinGroupSecondMsg(CustomerGroupMemberDTO memberDTO,PrivateSphere privateSphere, Long wid) {
        try {
            if(Objects.isNull(memberDTO)){
                log.error("CorpChatGroupServiceImpl.customerJoinGroupSecondMsg 群成员为null" );
                return;
            }
            // 查询店铺的主账号wid
            QueryBosRequestDTO queryBosRequestDTO = new QueryBosRequestDTO();
            queryBosRequestDTO.setBosId(privateSphere.getBosId());
            MerchantDTO merchantDTO = SoaUtil.unpack(bosAdapterService.queryWidByBosId(queryBosRequestDTO));
            if(Objects.isNull(merchantDTO) || Objects.isNull(merchantDTO.getWid())){
                log.error("CorpChatGroupServiceImpl.customerJoinGroupSecondMsg 根据bosId换取店铺主账号wid失败" );
                return;
            }
            OpenMessageDTO openMessageDTO = new OpenMessageDTO();
            openMessageDTO.setId(privateSphere.getBosId() + "-" + memberDTO.getGroupId()+"-" + memberDTO.getMemberId()+"-" + memberDTO.getJoinTime().getTime());
            openMessageDTO.setTopic(IpaasMsgConstant.Topic.IPAAS_CUSTOMER_GROUP);
            openMessageDTO.setEvent(IpaasMsgConstant.Event.ADD_MEMBER);
            openMessageDTO.setBosId(privateSphere.getBosId());
            openMessageDTO.setWid(merchantDTO.getWid());
            openMessageDTO.setOriginProductId(privateSphere.getProductId());
            openMessageDTO.setOriginProductInstanceId(privateSphere.getProductInstanceId());
            JoinGroupMsgModel joinGroupMsgModel = new JoinGroupMsgModel();
            JoinGroupMsgConvert.convertJoinGroupMsgModel(memberDTO, wid, joinGroupMsgModel,privateSphere);
            openMessageDTO.setMsgBody(JSON.toJSONString(joinGroupMsgModel));
            openMessageDubboService.sendOpenMessage(openMessageDTO);
        } catch (Exception e) {
            log.error("客户入群消息二次分发对接开放平台 error: ", e);
        }
    }

    /**
     * 构成 CustomerGroupMemberList
     * @param memberDTO
     * @param groupDTO
     * @return
     */
    private CustomerGroupMemberList composeCustomerGroupMemberList(CustomerGroupMemberDTO memberDTO,CorpChatGroupDTO groupDTO){
        CustomerGroupMemberList customerGroupMemberList = new CustomerGroupMemberList();
        customerGroupMemberList.setUpdateDetail(GroupUpdateDetailConstant.ADD_MEMBER);
        if (Boolean.TRUE.equals(groupDTO.getIsInitGroup())) {
            customerGroupMemberList.setIsCurrent(false);
        }
        customerGroupMemberList.setMembers(Lists.newArrayList(memberDTO));
        return customerGroupMemberList;
    }

    /**
     * 客户群变更
     *
     * @return
     */
    public SoaResponse<?,?> update(CorpChatGroupDTO dto,PrivateSphere privateSphere) {
        if(Objects.isNull(dto) || Objects.isNull(dto.getCorpId()) || Objects.isNull(dto.getChatId())){
            log.error("CorpChatGroupServiceImpl.update 客户群创建参数异常");
            return SoaUtil.error(errorCode, null, null);
        }
        log.info("客户群变更,dto:{}", dto);

        // 获取私域
        if (Objects.isNull(privateSphere)) {
            privateSphere = privateSphereThird.fetch(dto.getCorpId(), dto.getTemplateId());
            if (Objects.isNull(privateSphere) || StringUtils.isEmpty(privateSphere.getPrivateSphereId())) {
                log.info("客户群变更，未查到私域信息，不进行客户数据同步，dto:{}", dto);
                return SoaUtil.error(errorCode, null, null);
            }
        }

        // 尝试获取分布式锁
        String redisKey = String.format(RedisLockConstant.CUSTOMER_GROUP_SYNC_LOCK, privateSphere.getBosId(), dto.getChatId());
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(redisKey, String.valueOf(System.currentTimeMillis()), RedisLockConstant.CUSTOMER_GROUP_SYNC_LOCK_EXPIRE, TimeUnit.SECONDS);
        if (!Boolean.TRUE.equals(flag)) {
            log.info("客户群变更，尝试获取分布式锁失败，当前客户群正在同步中dto:{}", dto);
            return SoaUtil.error(errorCode, null, null);
        }
        try {
            // 1.从DB查询本地客户群数据
            CustomerGroup localGroup = customerGroupExportServiceProxy.getGroupWithAnemicMember(privateSphere, dto.getChatId());
            Employee owner = null;
            if (Objects.nonNull(localGroup) && Objects.nonNull(localGroup.getOwner())) {
                owner = (Employee) localGroup.getOwner();
                // 实时消息 且 变更类型为：成员入群，成员退群，群名变更，群公告变更 才进行本地群主状态的校验
                if(isCheckOwnerStatus(dto)){
                    if (EmployeeStatusEnum.USER_UN_AUTHORIZATION == owner.getStatus() || EmployeeActiveStatusEnum.INACTIVE == owner.getActiveStatus()) {
                        log.info("客户群更新失败,群主未授权 dto: {}", JSON.toJSONString(dto));
                        return SoaUtil.error(errorCode, null, null);
                    }
                }
            }

            // 2.远程查询企微客户群数据（腾讯）
            SoaResponse<CustomerGroupDetailResBO, Void> soaResponse = getCustomerGroupDetailResBO(dto, privateSphere);
            CustomerGroupDetailResBO groupChat = soaResponse.getResponseVo();
            if (groupChat == null) {
                log.warn("客户群变更，查询企业微信接口未获得数据,dto:{}", dto);
                return SoaUtil.error(soaResponse.getReturnCode(), null, null);
            }

            // 根据腾讯返回的UserID再查下群主信息，在校正下，已腾讯侧数据为准
            owner = employeeProxy.fetchEmployeeById(privateSphere, groupChat.getOwner(), null);
            if(Objects.isNull(owner)){
                log.error("客户群更新失败，未查到群主信息 corpId: {}，userId: {}", dto.getCorpId(),groupChat.getOwner());
                return SoaUtil.error(errorCode, null, null);
            }
            GroupOwnerModel groupOwnerModel = getOwnerInfo(owner);

            // 3.从群信息里取出成员列表(腾讯)
            List<CustomerGroupMemberDTO> txMemberList = getTxMemberList(groupChat, privateSphere);

            // 4.统计客户群里的外部联系人总数、有效外部联系人总数
            ExternalUserNumModel externalUserNumModel = this.getExternalUserNum(txMemberList);

            // 6、更新群基础信息
            String groupId = null;
            String ownerId = null;
            if (Objects.nonNull(localGroup)) {
                groupId = localGroup.getId();
                ownerId = owner.getId();
            }
            CustomerGroupCreateRequest customerGroupCreateRequest = getCustomerGroupCreateRequest(dto.getCorpId(), groupChat, groupOwnerModel, externalUserNumModel, groupId, ownerId, privateSphere);
            Integer n = customerGroupExportServiceProxy.createCustomerGroup(customerGroupCreateRequest);
            if (n == 0) {
                log.error("客户群更新失败，customerGroupCreateRequest:{}", JSON.toJSONString(customerGroupCreateRequest));
                return SoaUtil.error(errorCode, null, null);
            }

            // 7、群成员数据对比
            List<CustomerGroupMemberDTO> allMembers = new ArrayList<>();
            // 新增的成员列表
            List<CustomerGroupMemberDTO> addMembers = new ArrayList<>();
            // 删除的成员列表
            List<CustomerGroupMemberDTO> deleteMembers = new ArrayList<>();
            // 更新的成员列表
            List<CustomerGroupMemberDTO> updateMembers = new ArrayList<>();
            // 充血员工信息 （后续数据对比时要用到员工的userID）
            List<CustomerGroupMember> localGroupMemberList = new ArrayList<>();
            if(Objects.nonNull(localGroup)){
                localGroupMemberList = localGroup.getGroupMembers();
                richEmployee(privateSphere, localGroupMemberList);
            }
            // 数据对比
            memberContrast(dto, localGroupMemberList, txMemberList, addMembers, deleteMembers, updateMembers, privateSphere);
            // 待更新的群成员数据对比 减少无效更新
            List<CustomerGroupMemberDTO> finalUpdateMembers = updateContrast(localGroupMemberList, updateMembers, dto, privateSphere);

            log.info("群变更 dto:{} deleteMembers.size:{}, updateMembers.size:{},finalUpdateMembers:{}, addMember.size:{}",
                    JSON.toJSONString(dto),
                    deleteMembers.size(), updateMembers.size(), finalUpdateMembers.size(), addMembers.size());

            // 8、执行增删改，把微盟DB的群成员数据和企业微信的对齐
            if(CollectionUtils.isNotEmpty(addMembers)){
                allMembers.addAll(addMembers);
            }
            if(CollectionUtils.isNotEmpty(deleteMembers)) {
                allMembers.addAll(deleteMembers);
            }
            if(CollectionUtils.isNotEmpty(finalUpdateMembers)) {
                allMembers.addAll(finalUpdateMembers);
            }
            if(CollectionUtils.isNotEmpty(allMembers)) {
                CustomerGroupMemberCreateListRequest customerGroupMemberUpdateListRequest = new CustomerGroupMemberCreateListRequest();
                customerGroupMemberUpdateListRequest.setPrivateSphere(privateSphere);
                customerGroupMemberUpdateListRequest.setMemberList(allMembers);
                customerGroupExportServiceProxy.batchCreateUpdateGroupMember(customerGroupMemberUpdateListRequest);
            }
            log.info("客户群变更成功,dto:{}", dto);

            // 9、后续动作
            updateAfter(dto, privateSphere, txMemberList, customerGroupCreateRequest, addMembers, deleteMembers);

            return SoaUtil.ok(null);
        } catch (BizException e) {
            if (normalErrorCodes.contains(e.getErrcode())) {
                log.info("正常业务逻辑下的客户群更新失败，info:", e);
            } else {
                log.error("客户群更新失败，error:", e);
            }
            return SoaUtil.error(errorCode, null, null);
        } catch (Exception e) {
            log.error("客户群更新失败，error:", e);
            return SoaUtil.error(errorCode, null, null);
        }
        finally {
            redisTemplate.delete(redisKey);
        }
    }

    /**
     * 更新客户群后续动作
     * @param dto
     * @param privateSphere
     * @param txMemberList
     * @param customerGroupCreateRequest
     * @param addMembers
     * @param deleteMembers
     */
    private void updateAfter(CorpChatGroupDTO dto, PrivateSphere privateSphere, List<CustomerGroupMemberDTO> txMemberList, CustomerGroupCreateRequest customerGroupCreateRequest, List<CustomerGroupMemberDTO> addMembers, List<CustomerGroupMemberDTO> deleteMembers) {
        if (!Boolean.TRUE.equals(dto.getIsInitGroup())) {
            // CDP 打点
            if (CollectionUtils.isNotEmpty(deleteMembers)) {
                CustomerGroupMemberEvent customerGroupMemberEvent = new CustomerGroupMemberEvent(GroupMemberEventEnum.QUIT_GROUP.getCode(), privateSphere, customerGroupCreateRequest, deleteMembers);
                applicationContext.publishEvent(customerGroupMemberEvent);
            }

            if (CollectionUtils.isNotEmpty(addMembers)) {
                CustomerGroupMemberEvent customerGroupMemberEvent = new CustomerGroupMemberEvent(GroupMemberEventEnum.JOIN_GROUP.getCode(), privateSphere, customerGroupCreateRequest, addMembers);
                applicationContext.publishEvent(customerGroupMemberEvent);
            }

            // 消息二次分发
            // 分发更新类型为change_owner : 群主变更 & change_name : 群名变更
            if (GroupUpdateDetailConstant.CHANGE_OWNER.equals(dto.getUpdateDetail()) || GroupUpdateDetailConstant.CHANGE_NAME.equals(dto.getUpdateDetail())) {
                ArtemisBaseInfo<String> artemisBaseDomain = new ArtemisBaseInfo<>();
                artemisBaseDomain.setTopic(ArtemisConstant.Topic.WECHATWORK_GROUP_UPDATE_MSG);
                artemisBaseDomain.setProducer(ArtemisConstant.USER);
                artemisBaseDomain.setEvent(JSON.toJSONString(customerGroupCreateRequest));
                artemisService.sendStringEventToArtemis(artemisBaseDomain);
            }
            // 记录今日退群客户数
            if (CollectionUtils.isNotEmpty(deleteMembers)) {
                List<CustomerGroupMemberDTO> deleteCustomerList = deleteMembers.stream()
                        .filter(e -> GroupMemberTypeEnum.EXTERNAL_USER.getCode().equals(e.getType()))
                        .collect(Collectors.toList());
                recordJoinQuitGroup(privateSphere, dto, deleteCustomerList, GroupRecordEnum.QUIT_GROUP);
            }
        }

        // 配置 全量群客户推送passport 或 新增群客户推送 passport
        List<CustomerGroupMemberDTO> sendPassportMemberList;
        if (Boolean.TRUE.equals(sendFullPassport)) {
            sendPassportMemberList = txMemberList;
        } else {
            sendPassportMemberList = addMembers;
        }

        // 将群成员中客户数据送到passPort & 发送wid创建完成消息
        if (CollectionUtils.isNotEmpty(sendPassportMemberList)) {
            sendPassPortAndNotify(privateSphere, sendPassportMemberList, dto);
        }

        // 分发更新类型为del_member : 成员退群
        if (CollectionUtils.isNotEmpty(deleteMembers)) {
            ArtemisBaseInfo<String> artemisBaseDomain = new ArtemisBaseInfo<>();
            artemisBaseDomain.setTopic(ArtemisConstant.Topic.WECHATWORK_GROUP_MEMBER_UPDATE_MSG);
            artemisBaseDomain.setProducer(ArtemisConstant.USER);
            CustomerGroupMemberList deletedMemberList = new CustomerGroupMemberList();
            deletedMemberList.setUpdateDetail(GroupUpdateDetailConstant.DEL_MEMBER);
            if (Boolean.TRUE.equals(dto.getIsInitGroup())) {
                deletedMemberList.setIsCurrent(false);
            }
            // 消息体中加bosId
            for (CustomerGroupMemberDTO deleteMember : deleteMembers) {
                deleteMember.setBosId(privateSphere.getBosId());
            }

            deletedMemberList.setMembers(deleteMembers);
            artemisBaseDomain.setEvent(JSON.toJSONString(deletedMemberList));
            artemisService.sendStringEventToArtemis(artemisBaseDomain);
        }

        // 记录今日进群客户
        if(CollectionUtils.isNotEmpty(txMemberList)){
            List<CustomerGroupMemberDTO> joinMemberList = txMemberList.stream()
                    .filter(e -> GroupMemberTypeEnum.EXTERNAL_USER.getCode().equals(e.getType()) && isToday(e.getJoinTime()))
                    .collect(Collectors.toList());
            recordJoinQuitGroup(privateSphere, dto, joinMemberList, GroupRecordEnum.JOIN_GROUP);
        }

        // 更新群成员好友标识
        if(CollectionUtils.isNotEmpty(addMembers)){
            customerGroupMemberStatisticThreadPool.execute(() -> customerGroupStatisticService.updateFriendIdentification(addMembers, privateSphere));
        }
    }

    /**
     * 检查变更类型
     * 只有当变更类型为：成员入群，成员退群，群名变更，群公告变更 才进行群主状态的校验
     * @return boolean
     */
    private boolean isCheckOwnerStatus(CorpChatGroupDTO dto) {
        if(Objects.isNull(dto) || Objects.isNull(dto.getUpdateDetail())){
            return false;
        }
        switch (dto.getUpdateDetail()){
            case GroupUpdateDetailConstant.ADD_MEMBER:
            case GroupUpdateDetailConstant.DEL_MEMBER:
            case GroupUpdateDetailConstant.CHANGE_NAME:
            case GroupUpdateDetailConstant.CHANGE_NOTICE:
                return true;
            default:
                return false;
        }
    }

    /**
     * 将群成员中的员工信息充血（后续数据对比时要用到员工的userID）
     *
     * @param privateSphere privateSphere
     * @param memberList    memberList
     */
    private void richEmployee(PrivateSphere privateSphere, List<CustomerGroupMember> memberList) {
        if(CollectionUtils.isEmpty(memberList)){
            return;
        }
        List<Employee> employeeByGroupMemberList = employeeProxy.getEmployeeByGroupMemberList(privateSphere, memberList);
        if (CollectionUtils.isNotEmpty(employeeByGroupMemberList)) {
            Map<String, Employee> employeeMap = employeeByGroupMemberList.stream().collect(Collectors.toMap(Employee::getId, Function.identity(), (k1, k2) -> k1));
            for (CustomerGroupMember customerGroupMember : memberList) {
                if (GroupMemberTypeEnum.EXTERNAL_USER.equals(customerGroupMember.getGroupMemberType())) {
                    continue;
                }
                Employee employee = employeeMap.get(customerGroupMember.getMemberId());
                if (Objects.nonNull(employee)) {
                    customerGroupMember.setEmployee(employee);
                }
            }
        }
    }

    /**
     * 群成员数据对比
     * 数据对比 本地群成员列表和腾讯返回的群成员列表 区分增删改
     * @param dto dto
     * @param localGroupMemberList localGroupMemberList
     * @param txMemberList txMemberList
     * @param addMembers addMembers
     * @param deleteMembers deleteMembers
     * @param updateUnionMembers updateUnionMembers
     */
    private void memberContrast(CorpChatGroupDTO dto, List<CustomerGroupMember> localGroupMemberList, List<CustomerGroupMemberDTO> txMemberList, List<CustomerGroupMemberDTO> addMembers, List<CustomerGroupMemberDTO> deleteMembers, List<CustomerGroupMemberDTO> updateUnionMembers ,PrivateSphere privateSphere) {
        // 本地群成员列表
        List<CustomerGroupMemberDTO> dbMemberList = new ArrayList<>();
        convertMemberDTO(localGroupMemberList, dto, dbMemberList, privateSphere);
        // 8.将企业微信和微盟DB里的群成员列表做比较
        //   a.两者都为空：那无需做任何处理
        //   b.企业微信那边的群成员列表为空，但微盟DB里的不为空
        if (CollectionUtils.isEmpty(txMemberList) && CollectionUtils.isNotEmpty(dbMemberList)) {
            // I.设置每个成员的删除标志为已删除
            for (CustomerGroupMemberDTO memberDTO : dbMemberList) {
                memberDTO.setDeleted(Boolean.TRUE);
                memberDTO.setQuitScene(dto.getQuitScene());
                memberDTO.setQuitTime(dto.getQuitDate());
            }
            // II.添加到待删除的列表
            deleteMembers.addAll(dbMemberList);
        }
        //   c.企业微信那边的群成员列表不为空，但微盟DB里的为空
        else if (CollectionUtils.isNotEmpty(txMemberList) && CollectionUtils.isEmpty(dbMemberList)) {
            // I.添加每个成员到待新增列表
            addMembers.addAll(txMemberList);
        }
        //   d.两者均不为空，那么需要对齐差异，以企业微信的数据为准
        else {
            // I.把腾讯返回的群成员列表转成 userId_type-{userId, type, joinTime, joinScene, unionId} 的map
            Map<String, CustomerGroupMemberDTO> txMemberListMap = txMemberList.stream().collect(Collectors.toMap(
                    k -> Joiner.on("_").join(k.getMemberId(), k.getType()),
                    Function.identity(), (k1, k2) -> k1));

            // II.循环微盟DB里查到的群成员列表
            for (CustomerGroupMemberDTO dbMember : dbMemberList) {
                // 1）看他们的userId_type在不在map里，不在的话说明企业微信的群成员列表里没有这个人，要在微盟的DB里删除
                CustomerGroupMemberDTO txMember =
                        txMemberListMap.get(Joiner.on("_").join(dbMember.getMemberId(), dbMember.getType()));
                if (txMember == null) {
                    // 1.1）打上删除标记
                    dbMember.setDeleted(Boolean.TRUE);
                    dbMember.setQuitScene(dto.getQuitScene());
                    dbMember.setQuitTime(dto.getQuitDate());
                    // 1.2）添加到待删除列表
                    deleteMembers.add(dbMember);
                }
                // 2）如果在，需要对齐企业微信和微盟存的群成员信息
                else {
                    updateUnionMembers.add(txMember);
                }
            }

            // III.真的佛了，再把微盟DB里存的群成员列表变成 userId_type-CorpChatGroupMember的map
            Map<String, CustomerGroupMemberDTO> dbMemberListMap = dbMemberList.stream().collect(Collectors.toMap(
                    k -> Joiner.on("_").join(k.getMemberId(), k.getType()),
                    Function.identity(), (k1, k2) -> k1));
            // 1）遍历从腾讯查到的群成员列表数据
            for (CustomerGroupMemberDTO txMember : txMemberList) {
                // 2）用userId_type去检查map里有没，没有的话说明企业微信里有这个人，微盟的DB里没有
                if (!dbMemberListMap.containsKey(Joiner.on("_").join(txMember.getMemberId(), txMember.getType()))) {
                    // 3）把这个人加到待添加列表
                    addMembers.add(txMember);
                }
            }
        }
    }

    private void convertMemberDTO(List<CustomerGroupMember> localGroupMemberList, CorpChatGroupDTO dto, List<CustomerGroupMemberDTO> dbMemberList , PrivateSphere privateSphere) {
        if(CollectionUtils.isEmpty(localGroupMemberList)){
            return;
        }
        for (CustomerGroupMember customerGroupMember : localGroupMemberList) {
            CustomerGroupMemberDTO customerGroupMemberDTO = getCustomerGroupMemberDTO(privateSphere, dto.getChatId(), customerGroupMember);
            dbMemberList.add(customerGroupMemberDTO);
        }
    }


    /**
     * 对比要更新的群成员 关键字段一致时无需更新
     *
     * @param localMembers  数据库中存的Member
     * @param txMembers 腾讯通过过来的Member
     * @param dto           正在处理的群
     * @return 真正要处理的Member
     */
    private List<CustomerGroupMemberDTO> updateContrast(List<CustomerGroupMember> localMembers, List<CustomerGroupMemberDTO> txMembers, CorpChatGroupDTO dto, PrivateSphere privateSphere) {
        List<CustomerGroupMemberDTO> finalMembers = new ArrayList<>();

        // 腾讯通过过来的Member为空
        if(CollectionUtils.isEmpty(txMembers)){
            return finalMembers;
        }
        // 数据库中存的Member为空 全部更新
        if(CollectionUtils.isEmpty(localMembers)){
            return txMembers;
        }

        // 模型转换
        List<CustomerGroupMemberDTO> dbMemberList = new ArrayList<>();
        convertMemberDTO(localMembers, dto, dbMemberList, privateSphere);
        Map<String, CustomerGroupMemberDTO> dbMemberMap = dbMemberList.stream()
                .collect(Collectors.toMap(e -> e.getMemberId() + "_" + e.getType(), Function.identity(), (k1, k2) -> k1));

        // 数据对比
        for (CustomerGroupMemberDTO txMember : txMembers) {
            CustomerGroupMemberDTO dbMember = dbMemberMap.get(txMember.getMemberId() + "_" + txMember.getType());
            if(Objects.isNull(dbMember)){
                finalMembers.add(txMember);
                continue;
            }
            // unionId
            boolean unionId = contrast(txMember.getUnionid(), dbMember.getUnionid(),String.class);
            // join_time
            boolean joinTime = contrast(txMember.getJoinTime(), dbMember.getJoinTime(),Date.class);
            // join_scene
            boolean joinScene = contrast(txMember.getJoinScene(),dbMember.getJoinScene(),Integer.class);
            // nickname
            boolean nickname = contrast(txMember.getGroupNickname(),dbMember.getGroupNickname(),String.class);
            // name
            boolean name = contrast(txMember.getNickname(),dbMember.getNickname(),String.class);
            if(unionId && joinTime && joinScene && nickname && name){
                continue;
            }
            finalMembers.add(txMember);
        }
        return finalMembers;
    }

    /**
     * 获取群主授权信息&vidList
     * @return GroupOwnerModel
     */
    private GroupOwnerModel getOwnerInfo(Employee employee) {
        GroupOwnerModel groupOwnerModel = new GroupOwnerModel();
        groupOwnerModel.setAuthorizationStatus(GroupAuthorizationStatusEnum.UNAUTHORIZED.getCode());
        groupOwnerModel.setVidList(new ArrayList<>());
        if (Objects.isNull(employee) || EmployeeStatusEnum.USER_UN_AUTHORIZATION == employee.getStatus() || EmployeeActiveStatusEnum.INACTIVE == employee.getActiveStatus()) {
            return groupOwnerModel;
        }
        //使用set合并员工所属vid节点和上级vid节点，防止重复
        Set<Long> finalVids = new HashSet<>();
        if (CollectionUtils.isNotEmpty(employee.getVidList())) {
            finalVids.addAll(employee.getVidList());
        }
        if (CollectionUtils.isNotEmpty(employee.getHigherVidList())) {
            finalVids.addAll(employee.getHigherVidList());
        }
        groupOwnerModel.setAuthorizationStatus(GroupAuthorizationStatusEnum.AUTHORIZED.getCode());
        groupOwnerModel.setVidList(new ArrayList<>(finalVids));
        return groupOwnerModel;
    }

    private List<CustomerGroupMemberDTO> getTxMemberList(CustomerGroupDetailResBO groupChat, PrivateSphere privateSphere) {
        List<CustomerGroupMemberDTO> txMemberList = new ArrayList<>();
        List<CustomerGroupDetailResBO.MemberListResponse> memberList = groupChat.getMemberList();
        if (CollectionUtils.isNotEmpty(memberList)) {
            for (CustomerGroupDetailResBO.MemberListResponse memberListResponse : memberList) {
                CustomerGroupMemberDTO customerGroupMemberDTO = new CustomerGroupMemberDTO();
                customerGroupMemberDTO.setCorpId(privateSphere.getCorpId());
                customerGroupMemberDTO.setGroupId(groupChat.getChatId());
                customerGroupMemberDTO.setMemberId(memberListResponse.getUserid());
                customerGroupMemberDTO.setType(memberListResponse.getType());
                customerGroupMemberDTO.setUnionid(memberListResponse.getUnionid());
                if(Objects.nonNull(memberListResponse.getJoinTime())){
                    customerGroupMemberDTO.setJoinTime(new Date(memberListResponse.getJoinTime() * 1000L));
                }
                customerGroupMemberDTO.setJoinScene(memberListResponse.getJoinScene());
                customerGroupMemberDTO.setNickname(memberListResponse.getName());
                customerGroupMemberDTO.setGroupNickname(memberListResponse.getGroupNickname());
                customerGroupMemberDTO.setState(memberListResponse.getState());
                if (Objects.nonNull(memberListResponse.getInvitor())) {
                    customerGroupMemberDTO.setInvitorOrgUserid(memberListResponse.getInvitor().getUserid());
                }
                txMemberList.add(customerGroupMemberDTO);
            }
        }
        return txMemberList;
    }

    private CustomerGroupMemberDTO getCustomerGroupMemberDTO(PrivateSphere privateSphere, String groupId, CustomerGroupMember customerGroupMember) {
        CustomerGroupMemberDTO customerGroupMemberDTO = new CustomerGroupMemberDTO();
        customerGroupMemberDTO.setCorpId(privateSphere.getCorpId());
        customerGroupMemberDTO.setGroupId(groupId);
        if (GroupMemberTypeEnum.EMPLOYEE == customerGroupMember.getGroupMemberType()) {
            customerGroupMemberDTO.setMemberId(customerGroupMember.getEmployee().getTxUserId());
            customerGroupMemberDTO.setType(GroupMemberTypeEnum.EMPLOYEE.getCode());
        } else if (GroupMemberTypeEnum.EXTERNAL_USER == customerGroupMember.getGroupMemberType()) {
            customerGroupMemberDTO.setMemberId(customerGroupMember.getCustomer().getExternalUserid());
            customerGroupMemberDTO.setUnionid(customerGroupMember.getCustomer().getUnionid());
            customerGroupMemberDTO.setType(GroupMemberTypeEnum.EXTERNAL_USER.getCode());
        }
        if (Objects.isNull(customerGroupMember.getJoinScene())) {
            // 1 - 由成员邀请入群（直接邀请入群）
            customerGroupMemberDTO.setJoinScene(JoinSceneEnum.DIRECT.getCode());
        } else {
            customerGroupMemberDTO.setJoinScene(customerGroupMember.getJoinScene().getCode());
        }
        customerGroupMemberDTO.setJoinTime(customerGroupMember.getJoinTime());
        customerGroupMemberDTO.setNickname(customerGroupMember.getNickname());
        customerGroupMemberDTO.setGroupNickname(customerGroupMember.getGroupNickname());
        customerGroupMemberDTO.setState(customerGroupMember.getState());
        if (Objects.nonNull(customerGroupMember.getInvitor())) {
            customerGroupMemberDTO.setInvitorOrgUserid(customerGroupMember.getInvitor().getId());
        }
        return customerGroupMemberDTO;
    }

    /**
     * 将群成员中客户数据送到passPort & 发送wid创建完成消息
     * @param privateSphere
     * @param sendPassportMemberList
     * @param dto
     */
    private void sendPassPortAndNotify(PrivateSphere privateSphere, List<CustomerGroupMemberDTO> sendPassportMemberList, @NotNull CorpChatGroupDTO dto) {
        // ①判断迁移状态
        boolean migrationStatus = getMigrationStatus(privateSphere.getBosId());
        if (migrationStatus) {
            // ②过滤出群成员中的客户
            List<CustomerGroupMemberDTO> sendPassportCustomerList = new ArrayList<>();
            for (CustomerGroupMemberDTO memberDTO : sendPassportMemberList) {
                memberDTO.setBosId(privateSphere.getBosId());
                if(GroupMemberTypeEnum.EXTERNAL_USER.getCode().equals(memberDTO.getType())){
                    sendPassportCustomerList.add(memberDTO);
                }
            }
            // ③具体处理
            sendToPassPortAndNotify(sendPassportCustomerList, dto, privateSphere);
        }
    }

    /**
     * 记录今日进群/退群客户
     *
     * @param dto           dto
     * @param memberDTOList memberDTOList
     * @param type          GroupRecordEnum
     */
    private void recordJoinQuitGroup(PrivateSphere privateSphere, CorpChatGroupDTO dto, List<CustomerGroupMemberDTO> memberDTOList, GroupRecordEnum type) {
        if (CollectionUtils.isEmpty(memberDTOList)) {
            return;
        }
        // 拼接key
        String countKey;
        String hashKey;
        // 当日入群客户
        if(GroupRecordEnum.JOIN_GROUP == type){
            countKey = String.format(RedisLockConstant.CUSTOMER_GROUP_JOIN_STATISTIC, privateSphere.getBosId(), dto.getChatId());
            hashKey = String.format(RedisLockConstant.CUSTOMER_GROUP_JOIN_STATISTIC_BOS, privateSphere.getBosId());
        }else {
            //  当日退群客户
            countKey = String.format(RedisLockConstant.CUSTOMER_GROUP_QUIT_STATISTIC, privateSphere.getBosId(), dto.getChatId());
            hashKey = String.format(RedisLockConstant.CUSTOMER_GROUP_QUIT_STATISTIC_BOS, privateSphere.getBosId());

        }
        // 去重
        List<String> stringList = CollUtils.map(memberDTOList, CustomerGroupMemberDTO::getMemberId);
        String[] strings = new String[stringList.size()];
        String[] memberIdArray = stringList.toArray(strings);
        redisTemplate.opsForSet().add(countKey, memberIdArray);
        redisTemplate.expireAt(countKey, LocalDateTime.of(LocalDate.now(), LocalTime.MAX).atZone(ZoneId.systemDefault()).toInstant());
        Long size = redisTemplate.opsForSet().size(countKey);
        // 记录 当日进/退群客户数
        if(Objects.nonNull(size)){
            HashOperations<String,String,Long> hashOperations = redisTemplate.opsForHash();
            hashOperations.put(hashKey, dto.getChatId(),size);
            redisTemplate.expireAt(hashKey, LocalDateTime.of(LocalDate.now(), LocalTime.MAX).atZone(ZoneId.systemDefault()).toInstant());
        }
    }


    /**
     * 客户群解散
     *
     */
    public void dismiss(CorpChatGroupDTO dto) {
        if(Objects.isNull(dto) || Objects.isNull(dto.getCorpId()) || Objects.isNull(dto.getChatId())){
            log.error("CorpChatGroupServiceImpl.dismiss 客户群创建参数异常");
            return;
        }
        log.info("客户群解散,dto:{}", dto);

        // 获取私域
        PrivateSphere privateSphere = privateSphereThird.fetch(dto.getCorpId(), dto.getTemplateId());
        if(Objects.isNull(privateSphere)){
            log.error("客户群解散，未查到私域信息，不进行客户数据同步，dto:{}", dto);
            return;
        }

        // 判断本地群是否存在 不存在无需更新
        CustomerGroup resultCustomerGroup = customerGroupExportServiceProxy.getGroupWithAnemicMember(privateSphere, dto.getChatId());
        if(Objects.isNull(resultCustomerGroup)){
            log.error("CorpChatGroupServiceImpl.dismiss 未查到客户群信息");
            return;
        }

        // 解散
        CustomerGroupCreateRequest customerGroupCreateRequest = new CustomerGroupCreateRequest();
        customerGroupCreateRequest.setCorpId(privateSphere.getCorpId());
        customerGroupCreateRequest.setPrivateSphere(privateSphere);
        customerGroupCreateRequest.setPrivateSphere(privateSphere);
        customerGroupCreateRequest.setGroupId(dto.getChatId());
        customerGroupCreateRequest.setState(ChatGroupStateEnum.DISMISS.getValue());
        customerGroupCreateRequest.setGroupType(CustomerGroupGroupTypeEnum.CUSTOMER_GROUP.getCode());
        if(Objects.nonNull(resultCustomerGroup.getOwner())){
            Employee owner = (Employee) resultCustomerGroup.getOwner();
            GroupOwnerModel groupOwnerModel = getOwnerInfo(owner);
            customerGroupCreateRequest.setVidList(groupOwnerModel.getVidList());
        }
        customerGroupExportServiceProxy.createCustomerGroup(customerGroupCreateRequest);
        log.info("客户群解散成功,dto:{}", dto);

        // CDP打点 客户群解散事件打点订阅
        customerGroupCreateRequest.setName(resultCustomerGroup.getName());
        if(Objects.nonNull(resultCustomerGroup.getOwner())){
            Employee owner = (Employee) resultCustomerGroup.getOwner();
            customerGroupCreateRequest.setOwner(owner.getTxUserId());
        }

        List<CustomerGroupMemberDTO> dbMemberList = new ArrayList<>();
        List<CustomerGroupMember> resultMemberList = resultCustomerGroup.getGroupMembers();
        convertMemberDTO(resultMemberList, dto, dbMemberList, privateSphere);
        CustomerGroupMemberEvent customerGroupMemberEvent = new CustomerGroupMemberEvent(GroupMemberEventEnum.QUIT_GROUP.getCode(),privateSphere,customerGroupCreateRequest,dbMemberList);
        applicationContext.publishEvent(customerGroupMemberEvent);
    }

    /**
     *
     * @param privateSphere
     * @param chatId
     * @return
     */
    private boolean isExistGroupChat(PrivateSphere privateSphere, String chatId) {
        CustomerGroup customerGroupReq = new CustomerGroup();
        customerGroupReq.setPrivateSphere(privateSphere);
        customerGroupReq.setGroupId(chatId);
        SingleResponse<CustomerGroup> singleResponse = customerGroupExportServiceProxy.fetchCustomerGroupDetail(customerGroupReq, false, new HashMap<>());
        CustomerGroup customerGroup = singleResponse.getData();
        return Objects.nonNull(customerGroup);
    }

    /**
     * 获取群成员【外部联系人】的数量
     *
     * @param memberList
     * @return
     */
    private ExternalUserNumModel getExternalUserNum(List<CustomerGroupMemberDTO> memberList) {
        Integer externalUserNum = 0;
        Integer validExternalUserNum = 0;

        if (CollectionUtils.isEmpty(memberList)) {
            return new ExternalUserNumModel(externalUserNum, validExternalUserNum);
        }

        // 1.成员-外部联系人总数量
        List<CustomerGroupMemberDTO> externalUserMembers = memberList.stream().filter(e -> e.getType() == ChatGroupMemberTypeEnum.EXTERNAL_USER.getValue()).collect(Collectors.toList());
        // 2.成员-外部联系人(unionId不为空)总数量
        List<CustomerGroupMemberDTO> validExternalUserMembers = externalUserMembers.stream().filter(e -> !StringUtils.isEmpty(e.getUnionid())).collect(Collectors.toList());

        return new ExternalUserNumModel(externalUserMembers.size(), validExternalUserMembers.size());
    }

    /**
     * 获取迁移状态
     * @param bosId
     * @return
     */
    public boolean getMigrationStatus(Long bosId){
        BatchMigrationStatusRequest batchMigrationStatusRequest = new BatchMigrationStatusRequest();
        batchMigrationStatusRequest.setBosId(Lists.newArrayList(bosId));
        List<BatchMigrationStatusVo> batchMigrationStatus = SoaUtil.unpack(ecAdapterService.batchMigrationStatus(batchMigrationStatusRequest));
        if (CollectionUtils.isNotEmpty(batchMigrationStatus)) {
            // 1: 迁移中
            BatchMigrationStatusVo batchMigrationStatusVo = batchMigrationStatus.get(0);
            if (batchMigrationStatusVo != null &&
                    batchMigrationStatusVo.getStatus() != null
                    && batchMigrationStatusVo.getStatus().equals(1)) {
                log.info("数据迁移中禁用同步 request: {}", JSON.toJSONString(batchMigrationStatus));
                return false;
            }
        }
        //可能已经迁移/或者不在迁移名单
        return true;

    }

    /**
     * 是否当日
     * @param joinTime
     * @return
     */
    private boolean isToday(Date joinTime) {
        long start = Date.from(LocalDateTime.of(LocalDate.now(), LocalTime.MIN).atZone(ZoneId.systemDefault()).toInstant()).getTime();
        long end = Date.from(LocalDateTime.of(LocalDate.now(), LocalTime.MAX).atZone(ZoneId.systemDefault()).toInstant()).getTime();
        return joinTime.getTime() >= start && joinTime.getTime() <= end;
    }

    /**
     * 对比数据相同
     * @param obj1
     * @param obj2
     * @param clazz
     * @return 是否相同 相同 true 不同false 都为null返回true
     */
    public static boolean contrast(Object obj1,Object obj2, Class<?> clazz){
        try{
            if (clazz == String.class) {
                String str1 = obj1 == null ? "" : obj1.toString();
                String str2 = obj2 == null ? "" : obj2.toString();
                if(str1.equals(str2)){
                    return true;
                }
            }
            if (Objects.isNull(obj1) && Objects.isNull(obj2)) {
                return true;
            }
            if(Objects.isNull(obj1) || Objects.isNull(obj2)){
                return false;
            }
            return clazz.cast(obj1).equals(clazz.cast(obj2));
        }catch (Exception e){
            log.error("CorpChatGroupServiceImpl.contrast error ",e);
        }
        return false;
    }

}
